#include "pch.hpp"
#include "Type.hpp"
#include "Reflectable.hpp"

#include <boostext/typedefs.hpp>

#undef interface

using namespace Engine;
using namespace Engine::Script;




Type::Type(boost::weak_ptr<const Reflectable> reflectable, pointer<asIObjectType> objectType)
	: m_reflectable(reflectable)
	, m_typeId(objectType->GetTypeId())
	, m_objectType(objectType)
{
	verify(m_objectType != nullptr);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

Type::Type(boost::weak_ptr<const Reflectable> reflectable, int typeId)
	: m_reflectable(reflectable)
	, m_typeId(typeId)
{}
///////////////////////////////////////////////////////////////////////////////////////////////////

Type::~Type()
{}
///////////////////////////////////////////////////////////////////////////////////////////////////




int Type::id() const
{
	return m_typeId;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

QString Type::name() const
{
	switch(m_typeId)
	{
	case asTYPEID_VOID:      return "void";
	case asTYPEID_BOOL:      return "bool";
	case asTYPEID_INT8:      return "int8";
	case asTYPEID_INT16:     return "int16";
	case asTYPEID_INT32:     return "int32";
	case asTYPEID_INT64:     return "int64";
	case asTYPEID_UINT8:     return "uint8";
	case asTYPEID_UINT16:    return "uint16";
	case asTYPEID_UINT32:    return "uint32";
	case asTYPEID_UINT64:    return "uint64";
	case asTYPEID_FLOAT:     return "float";
	case asTYPEID_DOUBLE:    return "double";
	default:
		{
			auto name = QString::fromUtf8(m_objectType->GetName());

			if(id() & asTYPEID_OBJHANDLE)          return QString("%1@").arg(name);
			else if(id() & asTYPEID_HANDLETOCONST) return QString("const %1@").arg(name);
			else                                   return name;
		}
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

std::size_t Type::size() const
{
	switch(m_typeId)
	{
	case asTYPEID_VOID:
		return 0;

	case asTYPEID_BOOL:
	case asTYPEID_INT8:
	case asTYPEID_UINT8:
		return 1;

	case asTYPEID_INT16:
	case asTYPEID_UINT16:
		return 2;

	case asTYPEID_INT32:
	case asTYPEID_UINT32:
	case asTYPEID_FLOAT:
		return 4;

	case asTYPEID_INT64:
	case asTYPEID_UINT64:
	case asTYPEID_DOUBLE:
		return 8;

	default:
		return m_objectType->GetSize();
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

bool Type::isPod() const
{
	switch(m_typeId)
	{
	case asTYPEID_VOID:
	case asTYPEID_BOOL:
	case asTYPEID_INT8:
	case asTYPEID_INT16:
	case asTYPEID_INT32:
	case asTYPEID_INT64:
	case asTYPEID_UINT8:
	case asTYPEID_UINT16:
	case asTYPEID_UINT32:
	case asTYPEID_UINT64:
	case asTYPEID_FLOAT:
	case asTYPEID_DOUBLE:
		return true;
	default:
		return false;
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

int Type::propertyCount() const
{
	switch(m_typeId)
	{
	case asTYPEID_VOID:
	case asTYPEID_BOOL:
	case asTYPEID_INT8:
	case asTYPEID_INT16:
	case asTYPEID_INT32:
	case asTYPEID_INT64:
	case asTYPEID_UINT8:
	case asTYPEID_UINT16:
	case asTYPEID_UINT32:
	case asTYPEID_UINT64:
	case asTYPEID_FLOAT:
	case asTYPEID_DOUBLE:
		return 0;
	default:
		return m_objectType->GetPropertyCount();
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

QString Type::value(const void* data) const
{
	switch(m_typeId)
	{
	case asTYPEID_VOID:   return QString();
	case asTYPEID_BOOL:   return (*(const bool*)(data)) ? "true" : "false"; break;
	case asTYPEID_INT8:   return QString::number(*(const int8*)(data)); break;
	case asTYPEID_INT16:  return QString::number(*(const int16*)(data)); break;
	case asTYPEID_INT32:  return QString::number(*(const int32*)(data)); break;
	case asTYPEID_INT64:  return QString::number(*(const int64*)(data)); break;
	case asTYPEID_UINT8:  return QString::number(*(const uint8*)(data)); break;
	case asTYPEID_UINT16: return QString::number(*(const uint16*)(data)); break;
	case asTYPEID_UINT32: return QString::number(*(const uint32*)(data)); break;
	case asTYPEID_UINT64: return QString::number(*(const uint64*)(data)); break;
	case asTYPEID_FLOAT:  return QString::number(*(const float*)(data)); break;
	case asTYPEID_DOUBLE: return QString::number(*(const double*)(data)); break;
	default:
		{
			auto object = reinterpret_cast<const asIScriptObject*>(data);

			// The script object (ie. the instance) should agree that it actually is the
			// type this ObjectType instance represents
			verify(m_objectType == pointer<asIObjectType>(object->GetObjectType()));

			QString v;
			QTextStream stream(&v);

			if(name() == "string")
			{
				// Well this sucks, we have to rely on the fact that QString is the registered string type
				// This code holds the right to explode in your face if I made a mistake...
				auto string = reinterpret_cast<const QString*>(data);
				stream << '"' << *string << '"';
			}
			else
			{
				stream << "{ ";

				// We use the following philosophy when evaluating the value of an object:
				// "An object is merely the sum of all it's properties"
				for(int i = 0; i < propertyCount(); ++i)
				{
					auto subType = property(i);

					// TODO: remove the const_cast when Witchlord adds a const overloaded function
					const void* property = const_cast<asIScriptObject*>(object)->GetAddressOfProperty(i);
					stream << QString::fromUtf8(object->GetPropertyName(i)) << "=" << subType.value(property) << " ";
				}

				stream << "}";
			}

			return v;
		}
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

bool Type::hasBase() const
{
	// TODO: Verify that this doesn't leak!!!
	return m_objectType->GetBaseType() != nullptr;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

Type::opt Type::base() const
{
	pointer<asIObjectType> base(m_objectType->GetBaseType());
	if(base)
		return reflectable()->type(base->GetTypeId());
	else
		return Type::opt();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

int Type::interfaceCount() const
{
	return m_objectType->GetInterfaceCount();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

Type Type::interface(int index) const
{
	verify(index < interfaceCount());

	auto type = pointer<asIObjectType>(m_objectType->GetInterface(index));
	return reflectable()->type(type->GetTypeId());
}
///////////////////////////////////////////////////////////////////////////////////////////////////

int Type::methodCount() const
{
	verify(isPod() == false);

	return m_objectType->GetMethodCount();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

Function Type::method(int index) const
{
	verify(index < methodCount());

	auto method = pointer<asIScriptFunction>(m_objectType->GetMethodDescriptorByIndex(index));
	return Function(reflectable(), method);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

Type Type::property(int index) const
{
	verify(m_objectType);
	verify(index < propertyCount());

	int id = m_objectType->GetPropertyTypeId(index);
	return reflectable()->type(id);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




boost::shared_ptr<const Reflectable> Type::reflectable() const
{
	return Reflectable::const_ptr(m_reflectable);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




void Type::compareState(boostext::iostreams::bin_istream& stream) const
{
	int tmp;
	stream >> tmp;
	if(tmp != id())
		throw invalid_value_exception("type id doesn't match", tmp);

	QByteArray data;
	stream >> data;
	QString n = QString::fromUtf8(data.constData(), data.size());
	if(n != name())
		throw invalid_value_exception("type name doesn't match", n);

	std::size_t s;
	stream >> s;
	if(s != size())
		throw invalid_value_exception("type size doesn't match", s);

	bool b;
	stream >> b;
	if(b != hasBase())
		throw invalid_value_exception("type's base doesn't match", b);

	if(hasBase())
	{
		stream >> tmp;
		if(tmp != base()->id())
			throw invalid_value_exception("type's base id doesn't match", tmp);
	}

	stream >> tmp;
	if(tmp != interfaceCount())
		throw invalid_value_exception("type interface count doesn't match", tmp);

	for(int i = 0; i < interfaceCount(); ++i)
		if(tmp != interface(i).id())
			throw invalid_value_exception("type interface id doesn't match", tmp);

	stream >> tmp;
	if(tmp != methodCount())
		throw invalid_value_exception("type method count doesn't match", tmp);

	for(int i = 0; i < methodCount(); ++i)
		if(tmp != method(i).id())
			throw invalid_value_exception("type method id doesn't match", tmp);

	stream >> tmp;
	if(tmp != propertyCount())
		throw invalid_value_exception("type property count doesn't match", tmp);

	for(int i = 0; i < propertyCount(); ++i)
		if(tmp != property(i).id())
			throw invalid_value_exception("type property id doesn't match", tmp);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Type::saveState(boostext::iostreams::bin_ostream& stream) const
{
	stream << id();
	stream << name().toUtf8();
	stream << size();
	stream << isPod();

	stream << hasBase();
	if(hasBase())
		stream << base()->id();

	stream << interfaceCount();
	for(int i = 0; i < interfaceCount(); ++i)
		stream << interface(i).id();

	stream << methodCount();
	for(int i = 0; i < methodCount(); ++i)
		stream << method(i).id();

	stream << propertyCount();
	for(int i = 0; i < propertyCount(); ++i)
		stream << property(i).id();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

boostext::iostreams::bin_istream& operator>>(boostext::iostreams::bin_istream& stream, QByteArray& data)
{
	int size;
	stream >> size;

	data.resize(size);
	stream.read(data.data(), size);

	return stream;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

boostext::iostreams::bin_ostream& operator<<(boostext::iostreams::bin_ostream& stream, const QByteArray& data)
{
	stream << data.size();
	for(int i = 0; i < data.size(); ++i)
		stream << data.at(i);

	return stream;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
